import { DslRule, native, NodePredicate, QvogHint } from 'qvog-dsl';
import { Rule, RuleType, TypeGuard } from './rule';
export { Rule, RuleType, TypeGuard } from './rule';
import { Queryable, QueryMeta } from '~/core/dsl';
import { Value } from 'qvog-types';
interface IRuleConvert {
  to_rule()
}
const SkipMatchExpr: NodePredicate<Value> = {
  is(node: Value): node is Value {
    return true;
  },
};
export class RuleChain<T = any> implements IRuleConvert {
  protected rules: Rule<any>[] = [];
  meta: QueryMeta = null

  when<U extends T>(guard: TypeGuard<T, U>): RuleChain<U>;

  when(predicate: (node: T) => boolean): RuleChain<T>;

  when(predicate: (node: T) => boolean): RuleChain<any> {
    this.rules.push({ type: 'condition', fn: predicate });
    return this as any;
  }

  select<U>(transformer: (node: T) => U): RuleChain<U> {
    this.rules.push({ type: 'transform', fn: transformer });
    return this as any;
  }

  constructor(meta: Partial<QueryMeta>) {
    this.meta = new QueryMeta(meta || { name: `rule-${(Math.random() * 1e8).toFixed(0)}` })
  }
  static create<T = any, C extends RuleChain<T> = RuleChain<T>>(this: new (meta: Partial<QueryMeta>) => C, meta: Partial<QueryMeta> = null): RuleChain<T> {
    return new this(meta);
  }

  to_rule(): DslRule<Queryable> {
    throw new Error('Method not implemented.');
  }
}

export class CpgRuleChain<T = any> extends RuleChain<T> {
  constructor(meta: Partial<QueryMeta> = null) {
    super(meta);
  }

  to_rule(): DslRule<Queryable> {
    throw new Error('Method not implemented.');
  }
}

export class QvogDslRuleChain<T = any> extends RuleChain<T> {
  constructor(meta: Partial<QueryMeta> = null) {
    super(meta);
  }

  to_rule(): DslRule<Queryable> {
    const stage1 = native<QvogHint>({ name: this.meta.name })()
      .match(SkipMatchExpr)
    const stage2 = stage1.when((node) => {
      return this.rules.every(r => {
        if (r.type === 'condition') {
          return r.fn(node);
        }
        node = r.fn(node);
        return true;
      });
    })
    return stage2.report({ message: this.meta.name });
  }
}